function painterSortStable(a, b) {
  if (a.groupOrder !== b.groupOrder) {
    return a.groupOrder - b.groupOrder;
  } else if (a.renderOrder !== b.renderOrder) {
    return a.renderOrder - b.renderOrder;
  } else if (a.program !== b.program) {
    return a.program.id - b.program.id;
  } else if (a.material.id !== b.material.id) {
    return a.material.id - b.material.id;
  } else if (a.z !== b.z) {
    return a.z - b.z;
  } else {
    return a.id - b.id;
  }
}

function reversePainterSortStable(a, b) {
  if (a.groupOrder !== b.groupOrder) {
    return a.groupOrder - b.groupOrder;
  } else if (a.renderOrder !== b.renderOrder) {
    return a.renderOrder - b.renderOrder;
  } else if (a.z !== b.z) {
    return b.z - a.z;
  } else {
    return a.id - b.id;
  }
}

function WebGLRenderList(properties) {
  const renderItems = [];
  let renderItemsIndex = 0;

  const opaque = [];
  const transparent = [];

  const defaultProgram = { id: -1 };

  function init() {
    renderItemsIndex = 0;

    opaque.length = 0;
    transparent.length = 0;
  }

  function getNextRenderItem(object, geometry, material, groupOrder, z, group) {
    let renderItem = renderItems[renderItemsIndex];
    const materialProperties = properties.get(material);

    if (renderItem === undefined) {
      renderItem = {
        id: object.id,
        object,
        geometry,
        material,
        program: materialProperties.program || defaultProgram,
        groupOrder,
        renderOrder: object.renderOrder,
        z,
        group,
      };

      renderItems[renderItemsIndex] = renderItem;
    } else {
      renderItem.id = object.id;
      renderItem.object = object;
      renderItem.geometry = geometry;
      renderItem.material = material;
      renderItem.program = materialProperties.program || defaultProgram;
      renderItem.groupOrder = groupOrder;
      renderItem.renderOrder = object.renderOrder;
      renderItem.z = z;
      renderItem.group = group;
    }

    renderItemsIndex++;

    return renderItem;
  }

  function push(object, geometry, material, groupOrder, z, group) {
    const renderItem = getNextRenderItem(object, geometry, material, groupOrder, z, group);

    (material.transparent === true ? transparent : opaque).push(renderItem);
  }

  function unshift(object, geometry, material, groupOrder, z, group) {
    const renderItem = getNextRenderItem(object, geometry, material, groupOrder, z, group);

    (material.transparent === true ? transparent : opaque).unshift(renderItem);
  }

  function sort(customOpaqueSort, customTransparentSort) {
    if (opaque.length > 1) opaque.sort(customOpaqueSort || painterSortStable);
    if (transparent.length > 1) transparent.sort(customTransparentSort || reversePainterSortStable);
  }

  function finish() {
    // Clear references from inactive renderItems in the list

    for (let i = renderItemsIndex, il = renderItems.length; i < il; i++) {
      const renderItem = renderItems[i];

      if (renderItem.id === null) break;

      renderItem.id = null;
      renderItem.object = null;
      renderItem.geometry = null;
      renderItem.material = null;
      renderItem.program = null;
      renderItem.group = null;
    }
  }

  return {
    opaque,
    transparent,

    init,
    push,
    unshift,
    finish,

    sort,
  };
}

function WebGLRenderLists(properties) {
  let lists = new WeakMap();

  function get(scene, renderCallDepth) {
    let list;

    if (lists.has(scene) === false) {
      list = new WebGLRenderList(properties);
      lists.set(scene, [list]);
    } else if (renderCallDepth >= lists.get(scene).length) {
      list = new WebGLRenderList(properties);
      lists.get(scene).push(list);
    } else {
      list = lists.get(scene)[renderCallDepth];
    }

    return list;
  }

  function dispose() {
    lists = new WeakMap();
  }

  return {
    get,
    dispose,
  };
}

export { WebGLRenderLists, WebGLRenderList };
